﻿using KonachanChanger.API;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;

namespace KonachanChanger.Data
{
    [DataContract]
  public class LocalDb
    {
        private static LocalDb _Instance = null;
        private LocalDb()
        {
            InitNullVars(new StreamingContext());
        }
        [OnDeserialized]
        private void InitNullVars(StreamingContext c)
        {
            if (Posts == null)
                Posts = new SortedDictionary<int, post>();
            if (BlockedIds == null)
                BlockedIds = new List<int>();
            if (searches == null)
                searches = new Dictionary<string, List<int>>();
            //if (SearchMetadata == null)
            //    SearchMetadata = new Dictionary<string,SearchData>();
            if (Tags == null)
                Tags = new SortedDictionary<string, TagCollection.TagInfo>();
            if (favoriteTags == null)
                favoriteTags = new List<string>();
            if (Ratings == null)
                Ratings = new SortedDictionary<int, char>();
            if (customTags == null)
                customTags = new SortedDictionary<int, string>();
            this.Prefs = UserPrefs.GetInstance();
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity")]
        public static LocalDb GetInstance()
        {
            lock (typeof(LocalDb))
            {
                if (_Instance == null)
                    _Instance = Serialization.TryReadObject<LocalDb>("localdb.json") ?? new LocalDb();
                return _Instance;
            }
        }

        [DataMember]
        private SortedDictionary<int, post> Posts;

        [DataMember]
        private Dictionary<string, List<int>> searches;

        //[DataMember]
        //private Dictionary<string, SearchData> SearchMetadata;

        [DataMember]
        public SortedDictionary<string, TagCollection.TagInfo> Tags;

        [DataMember]
        public List<int> BlockedIds;

        [DataMember]
        List<string> favoriteTags;


        public post this[int id]
        {
            get
            {
                return this.Posts[id];
            }
            set
            {
                lock (this)
                {
                    this.Posts[id] = value;
                    changed = true;
                }
            }
        }

        private bool _dirty;
        public bool changed
        {
            set
            {
                if (_dirty)
                    return;
                _dirty = true;
                ThreadPool.QueueUserWorkItem((o) => { 
                    Thread.Sleep(5000); 
                    lock (this) { 
                        Serialization.WriteObject("localdb.json", this);
                        Serialization.WriteObject("localdb.jzon", this); 
                    }
                    _dirty = false;
                });
            }
        }

        public struct SearchData
        {
            public int Count;

        }

        internal posts LocalSearch(string tags)
        {
            if (tags.Contains('~')) // Ick. I don't implement this yet.
            {
                posts ret = new posts();
                if (this.searches.ContainsKey(tags))
                {
                    List<post> posts = new List<post>();
                    foreach (var id in this.searches[tags])
                    {
                        posts.Add(this[id]);
                    }
                    ret.Posts = posts.ToArray();
                }
                if (ret.Posts != null && ret.Posts.Length > 0)
                    return ret;
            }
            return DoLocalSearch(tags);
        }

        private posts DoLocalSearch(string search)
        {
            List<post> found = new List<post>();
            string[] tags = search.ToLower().Split(' ');
            bool UseOptionals = search.Contains("~");
            foreach (var post in Posts.Values.ToArray())
            {
                var ptags = post.Tags;
                bool match = true;
                bool matchedOptionals = false;
                char checkDownloaded = default(char);
                foreach (var tag in tags)
                {
                    if (tag.StartsWith("rating:") || tag.StartsWith("-rating:"))
                    {
                        continue;
                    }
                    if (tag.StartsWith("downloaded:"))
                    {
                        checkDownloaded = tag[11];
                        if (checkDownloaded == 't') // true
                            checkDownloaded = 'y'; // yes
                        continue;
                    }
                    if (tag.StartsWith("md5:"))
                    {
                        if (post.md5 != tag.Substring(4))
                            match = false;
                        continue;
                    }
                    if (tag.StartsWith("-"))
                    {
                        if (ptags.Contains(tag.Substring(1)))
                            match = false;
                        continue;
                    }
                    if (tag.StartsWith("~"))
                    {
                        if (ptags.Contains(tag.Substring(1)))
                            matchedOptionals = true;
                        continue;
                    }
                    if (!ptags.Contains(tag))
                        match = false;
                }
                if (matchedOptionals != UseOptionals)
                    continue;
                if (!MatchesRating(post, search))
                    continue;

                if (match == true && checkDownloaded != default(char))
                {
                    bool y = checkDownloaded == 'y';
                    if (post.Downloaded != y)
                        match = false;
                    checkDownloaded = default(char);
                }
                if (match == true)
                    found.Add(post);
            }
            return new posts() { Posts = found.ToArray(), count = found.Count };
        }

        public post[] AllPosts
        {
            get
            {
                return Posts.Values.ToArray();
            }
        }

        internal static bool MatchesRating(post post, string searchstring)
        {
            string[] tags = searchstring.Split(' ');
            foreach (var tag in tags)
            {
                if (tag.StartsWith("rating:"))
                {
                    return tag.Last() == post.rating[0];
                }
                else if (tag.StartsWith("-rating:"))
                {
                    return tag.Last() != post.rating[0];
                }
            }
            return true;
        }

        internal void SetTagData(API.TagCollection.TagInfo tag)
        {
            if (tag.Name == null)
                return;
            lock (this)
                Tags[tag.Name] = tag;
            this.changed = true;
        }
        public string[] FavoriteTags { get { return favoriteTags.ToArray(); } }

        public void AddFavoriteTag(string tag)
        {
           ThreadPool.QueueUserWorkItem((e) => API.TagCollection.LoadTagInfo(tag));
           favoriteTags.Add(tag.ToLower());
           changed = true;
        }

        internal void AddPostToSearch(int p, string tags)
        {
            if (!searches.ContainsKey(tags))
                lock (this)
                    searches.Add(tags, new List<int>());
            if (!searches[tags].Contains(p))
                lock (this)
                    searches[tags].Add(p);
        }

        internal UserPrefs Prefs { get; set; }

        internal string CustomRating(int p)
        {
            if (Ratings.ContainsKey(p))
                return Ratings[p].ToString();
            return null;
        }
        [DataMember]
        private SortedDictionary<int, char> Ratings;

        internal string[] CustomTags(int p)
        {
            if (!this.customTags.ContainsKey(p))
                return new string[] { };
            return customTags[p].Split(' ');
        }
        [DataMember]
        private SortedDictionary<int, string> customTags;

    }
}
